package IC.TypeTable;

import java.util.*;

import IC.SymbolTable.SemanticError;
import IC.TypeTable.*;
import IC.AST.*;

// CHECK THIS    ///////////////////////////

/** Holds the type table for the input program */
public class TypeTable {

	private static Map<String,Type> mapPrimitiveTypes = new LinkedHashMap<String,Type>();
	private static Map<String,ClassType> mapClassTypes = new LinkedHashMap<String,ClassType>();

	private static Map<String,MethodType> mapMethodTypes = new LinkedHashMap<String,MethodType>();
	private static Map<Type,ArrayType> mapArrayTypes = new LinkedHashMap<Type,ArrayType>();

	protected static int typeCounter = 0;
	private static String fileName = null;  //file name



	//return true if the name is of a primitive type (except null or string).
	public static boolean isPrimitive(String name){
		return ((name == "int") || (name == "boolean") || (name == "void"));
	}

	public static String getFileName(){
		return fileName;
	}

	public static Map<String, Type> getMapPrimitiveTypes() {
		return mapPrimitiveTypes;
	}


	public static ArrayType addArrayType(Type elemType) {
		// check if array type object is already created
		if (mapArrayTypes.containsKey(elemType))
		{
			return mapArrayTypes.get(elemType);
		}
		else 
		{
			//  create put and return 
			ArrayType array_type = new ArrayType(elemType);
			mapArrayTypes.put(elemType,array_type);
			return array_type;
		}
	}

	public static MethodType addMethodType(Type retType, List<Type> listTypes){
		MethodType method_type1 = new MethodType(retType,listTypes);
		String key = method_type1.toString();

		MethodType method_type2 = mapMethodTypes.get(key);
		//check if method already in mapMethodTypes, if not--> add it
		if (method_type2 == null)
		{
			mapMethodTypes.put(key, method_type1);
			return method_type1;
		}
		else 
			return method_type2;	
	}


	public static void initTypeTable(String fileName) throws SemanticError
	{
		TypeTable.fileName = fileName;
		//create couple for each primitive type
		mapPrimitiveTypes.put("int", new IntType());
		mapPrimitiveTypes.put("boolean", new BoolType());
		mapPrimitiveTypes.put("null", new NullType());
		mapPrimitiveTypes.put("string", new StringType());
		mapPrimitiveTypes.put("void", new VoidType());	

		try
		{
			mapArrayTypes.put(getType("string"), new ArrayType(getType("string")));
			
			List l= new LinkedList<Type>();
			l.add(getType("string[]"));

			mapMethodTypes.put("{string[] -> void}", new MethodType(getType("void"), l) );
		}
		catch  (SemanticError err)
		{
			throw new SemanticError ("initTypeTable: " +err);
		}
	}

	/**
	 * gets a String 
	 * returns type of the String
	 **/
	public static Type getType(String typeName) throws SemanticError{

		Type type;
		//first search in  mapPrimitiveTypes if is't there next search in
		// mapArrayTypes  and next in mapClassTypes/
		type = mapPrimitiveTypes.get(typeName);
		if (type != null) 
		{
			return type;
		}
		if (typeName.endsWith("[]")) 
			return addArrayType(getType(typeName.substring(0, typeName.length()-2)));
		else 
			//return getClassType(typeName);
		{
			ClassType class_type = mapClassTypes.get(typeName);
			//check if class is defined
			if (class_type == null)
			{
				throw new SemanticError("Class: " + typeName + "  is not defined" );
			}
			else 
				return class_type;
		}
	}


	//////////////////   O.K ////////////// 
	/**
	 *Adds a new ClassType entry to TypeTable.
	 *each class can be defined only once
	 *If the class is already defined or extends a class that was not previously defined, throws SemanticError. 
	 */
	public static void addClassType(ICClass c) throws SemanticError
	{
		//check if class is already defined
		if (mapClassTypes.containsKey(c.getName()))
		{ 
			throw new SemanticError("Class: " + c.getName()+ "  is already defined",c.getLine());
		}
		//check if extends a class that is not defined
		if (c.hasSuperClass()) 
		{
			if (!mapClassTypes.containsKey(c.getSuperClassName()))
				throw new SemanticError("Super class: " + c.getSuperClassName() + " is not defined" ,c.getLine());
		}

		ClassType class_type = new ClassType(c);
		mapClassTypes.put(c.getName(),class_type);
	}

	/**
	 *returns unique class type object
	 */
	public static ClassType getClassType(String typeName) throws SemanticError{
		ClassType class_type = mapClassTypes.get(typeName);
		//check if class is defined
		if (class_type == null)
		{
			throw new SemanticError("Class: " + typeName + "  is not defined" );
		}
		else 
			return class_type;
	}

	/**
	 *returns string  for the TypeTable for "-dump-symtab"  
	 *NAME WAS CHANGED to typeTableToString
	 */

	public static String typeTableToString(){

		String typeTableString = "Type Table: "+ fileName + "\n";

		//primitive types
		String primitiveStr = "";

		for (Type p_type : mapPrimitiveTypes.values())
		{
			primitiveStr += "\t" + p_type.getTypeNumber() + ": Primitive type: "+p_type.getName() + "\n";
		}

		//method types
		String methodStr = "";	
		for (MethodType method_type: mapMethodTypes.values())
		{
			methodStr += "\t" + method_type.getTypeNumber() + ": Method type: "+method_type.toString() + "\n";
		}

		//class types
		String classStr = "";
		for (ClassType class_type: mapClassTypes.values())
		{
			classStr += "\t" + class_type.getTypeNumber() + ": Class: "+class_type.toString() + "\n";
		}

		//array types
		String arrayStr = "";
		for (ArrayType array_type: mapArrayTypes.values())
		{
			arrayStr += "\t" + array_type.getTypeNumber()+": Array type: " +array_type.toString() + "\n";
		}

		typeTableString +=primitiveStr + classStr + arrayStr + methodStr;
		return typeTableString;
	}



}
